package com.defangit.production.orm;

import com.defangit.production.bll.TimeSheetBll;
import com.defangit.production.domain.*;
import laogen.hibernate.CommonFinder;
import laogen.hibernate.CommonService;
import laogen.lang.exception.ParameterNotValidException;
import org.hibernate.Criteria;
import org.hibernate.criterion.*;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by zhangfagen on 2018/6/12.
 */
public class TimeSheetService extends CommonService<TimeSheet> implements TimeSheetBll {
    /**
     * 开始工时记录
     *
     * @param userId    工时记录涉及的员工id
     * @param processId 工时记录涉及的工序id
     * @return 增加的工时记录
     */
    @Override
    public TimeSheet beginTimeSheet(int userId, int processId) {
        Operator operator=getSession().get(Operator.class,userId);
        ProductionProcess process=getSession().get(ProductionProcess.class,processId);
        if(operator==null) throw ParameterNotValidException.zeroIdOrEntityNonExist("工时记录人");
        if(process==null) throw ParameterNotValidException.zeroIdOrEntityNonExist("产品工序");
        long cnt=findAll().user(userId).done(false).count();
        if(cnt>0){
            throw ParameterNotValidException.constraintNotSatisfy("工时记录","当前用户有尚未完成的工时记录");
        }
        TimeSheet sheet=new TimeSheet()
                .setOperator(operator)
                .setProcess(process);
        add(sheet);
        commitSession();
        return get(sheet.getId()).orElseThrow(()->
            ParameterNotValidException.zeroIdOrEntityNonExist("工时记录")
        );
    }

    public TimeSheet beginTimeSheet(int userId) {
        Operator operator = new OperatorService().get(userId).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("用户信息异常"));
        long cnt=findAll().user(userId).done(false).startTimeBetween(LocalDate.now().atStartOfDay(),LocalDateTime.now()).count();
        if(cnt>0){
            throw ParameterNotValidException.constraintNotSatisfy("工时记录","今日还有未签退记录,请先签退");
        }
        TimeSheet timeSheet = new TimeSheet()
                .setOperator(operator)
                .setStartTime(LocalDateTime.now());
        add(timeSheet);
        commitSession();
        return get(timeSheet.getId()).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("签到信息异常"));
    }

    /**
     * 结束工时记录
     *
     * @param userId 工时记录涉及的员工id
     * @return 是否成功结束工时记录
     */
    @Override
    public boolean endTimeSheet(int userId) {
        List<TimeSheet> sheets=findAll().user(userId).done(false).idDesc().matchedAll();
        if(sheets.size()==0){
            throw ParameterNotValidException.constraintNotSatisfy("工时记录","当前用户没有可完成的工时记录");
        }
        if(sheets.size()>1){
            throw ParameterNotValidException.constraintNotSatisfy("工时记录","当前用户未完成工时记录数据不唯一");
        }
        LocalDateTime now=LocalDateTime.now();
        sheets.get(0).setEndTime(now);
        sheets.get(0).setPeriod(
            Duration.between(sheets.get(0).getStartTime(),now).getSeconds()/3600
        );
        update(sheets.get(0));
        commitSession();
        return true;
    }

    public boolean signOutTimeSheet() {

        return true;
    }

    @Override
    public boolean endTimeSheet(int timeSheetId, List<ProcessTime> processTimes) {
        TimeSheet timeSheet = get(timeSheetId).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("签到信息异常"));
        LocalDateTime now=LocalDateTime.now();
        timeSheet.setEndTime(now).getProcessTimes().addAll(processTimes);
        timeSheet.setPeriod(
                Duration.between(timeSheet.getStartTime(),now).getSeconds()/3600
        );
        processTimes.forEach(processTime -> {
            processTime.getStockAlterations().parallelStream().forEach(stockAlteration -> {
                stockAlteration.setFlag(1).setQuantity(-stockAlteration.getQuantity());
            });
        });
        update(timeSheet);
        commitSession();
        return true;
    }


    /**
     * 补录工时记录
     *
     * @param sheet 要补录的工时记录
     * @return 是否成功补录工时记录
     */
    @Override
    public boolean makeUpTimeSheet(TimeSheet sheet) {
        if(sheet==null) throw ParameterNotValidException.nullParameter("工时记录");
        if(sheet.getStartTime()==null) throw ParameterNotValidException.nullField("工时记录-开始时间");
        if(sheet.getOperator()==null ||sheet.getOperator().getId()==0){
            throw ParameterNotValidException.nullField("工时记录-用户");
        }
        if(sheet.getProcess()==null||sheet.getProcess().getId()==0){
            throw ParameterNotValidException.nullField("工时记录-工序");
        }
        LocalDateTime now= LocalDateTime.now();
        if(now.compareTo(sheet.getStartTime())<0){
            throw ParameterNotValidException.constraintNotSatisfy("工时记录","不能补录未来的工时记录");
        }
        long period=Duration.between(sheet.getStartTime(),now).getSeconds();
        if(period>3600*24*30){
            throw ParameterNotValidException.constraintNotSatisfy("工时记录","不能补录30天以前的工时记录");
        }
        if(sheet.getEndTime()==null){
            long cnt=findAll().user(sheet.getOperator().getId()).done(false).count();
            if(cnt>0){
                throw ParameterNotValidException.constraintNotSatisfy("工时记录","补录的工时记录未完成且用户已有相应未完成工时记录");
            }
        }
        add(sheet);
        commitSession();
        return true;
    }

    /**
     * 修改工时记录涉及的用户
     *
     * @param timeSheetId 要修改的工时记录id
     * @param userId      工时记录目标用户id
     * @return 是否成功修改工时记录用户
     */
    @Override
    public boolean changeUser(int timeSheetId, int userId) {
        return false;
    }

    /**
     * 更改工时记录涉及的工序
     *
     * @param timeSheetId         要修改的工时记录id
     * @param productionProcessId 工时记录涉及的工序id
     * @return 是否成功修改工时记录涉及的工序
     */
    @Override
    public boolean changeProcess(int timeSheetId, int productionProcessId) {
        return false;
    }

    /**
     * 更改工时记录
     *
     * @param sheet 要修改的工时记录
     * @return 是否成功更改工时记录
     */
    @Override
    public boolean changeTimeSheet(TimeSheet sheet) {
        return false;
    }

    /**
     * 查找已签到工时记录
     *
     * @param operatorId 签到用户id
     * @return 用户已签到的工时记录
     */
    @Override
    public TimeSheet signed(int operatorId) {
        return findAll().user(operatorId).done(false).unique().orElse(null);
    }

    /**
     * 删除目标工时记录
     *
     * @param sheetId 要删除的工时记录id
     * @return 是否成功删除工时记录
     */
    @Override
    public boolean deleteTimeSheet(int sheetId) {
        return false;
    }

    @Override
    public TimeSheetFinder findAll() {
        return new TimeSheetFinderImpl();
    }

    public class TimeSheetFinderImpl extends CommonFinder<TimeSheet> implements TimeSheetFinder{
        private DetachedCriteria criteria;
        public TimeSheetFinderImpl(){
            this.criteria=detachedAll();
        }
        /**
         * 根据用户id区分工时记录查询
         *
         * @param userId 要查询的用户id
         * @return 区分用户后的工时记录查询
         */
        @Override
        public TimeSheetFinder user(Integer userId) {
            if(userId==null || userId==0) return this;
            criteria=criteria.add(Restrictions.eq("operator.id",userId));
            return this;
        }

        /**
         * 根据工序id区分工时记录查询
         *
         * @param processId 要查询的产品工序id
         * @return 区分工序后的工时记录查询
         */
        @Override
        public TimeSheetFinder process(Integer processId) {
            if(processId==null ||processId==0) return this;
            criteria=criteria.add(Restrictions.eq("process.id",processId));
            return this;
        }

        /**
         * 根据产品订单id区分工时记录查询
         *
         * @param billId 要查询的产品订单id
         * @return 区分产品订单后的工时记录查询
         */
        @Override
        public TimeSheetFinder bill(Integer billId) {
            if(billId==null || billId==0) return this;
            DetachedCriteria ofBill=DetachedCriteria.forClass(ProductionBill.class)
                    .createAlias("processes","process")
                    .add(Restrictions.idEq(billId))
                    .setProjection(Projections.property("process.id"));
            criteria=criteria.add(Subqueries.propertyIn("process.id",ofBill));
            return this;
        }

        /**
         * 工序工时记录起止时间区分工时记录查询
         *
         * @param startTime 要查询的工时记录起始时间
         * @param endTime   要查询的工时记录结束时间
         * @return 区分起止时间后的工时记录查询
         */
        @Override
        public TimeSheetFinder timeBetween(LocalDateTime startTime, LocalDateTime endTime) {
            if(startTime!=null){
                criteria=criteria.add(Restrictions.ge("startTime",startTime));
            }
            if(endTime!=null){
                criteria=criteria.add(Restrictions.le("endTime",endTime));
            }
            return this;
        }

        /**
         * 根据完成状态区分工时记录查询
         *
         * @param isDone 要查询的工时记录是否已完成
         * @return 区分完成状态后的工时记录查询
         */
        @Override
        public TimeSheetFinder done(Boolean isDone) {
            if(isDone==null) return this;
            Criterion criterion=Restrictions.isNull("endTime");
            if(isDone) criterion=Restrictions.not(criterion);
            criteria=criteria.add(criterion);
            return this;
        }

        @Override
        public TimeSheetFinder idDesc(){
            this.criteria = this.criteria.addOrder(Order.desc("id"));
            return this;
        }

        @Override
        public TimeSheetFinder startTimeBetween(LocalDateTime min, LocalDateTime max) {
            if (min!=null) {
                this.criteria = this.criteria.add(Restrictions.ge("startTime",min));
            }
            if (max!=null) {
                this.criteria = this.criteria.add(Restrictions.le("startTime",max));
            }
            return this;
        }

        @Override
        public Criteria getCriteria() {
            return criteria.getExecutableCriteria(getSession());
        }

        @Override
        public DetachedCriteria getDetachedCriteria() {
            return criteria;
        }
    }
}
